//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/GLshell/StateManager.h"

namespace Engine
{
namespace Graphics
{
	
	struct gl_shader
	{
		enum type
		{
			_UNKNOWN	= 0,
			VERTEX		= gles::GL_VERTEX_SHADER,
			FRAGMENT	= gles::GL_FRAGMENT_SHADER,
		};
	};


	struct gl_shader_precission
	{
		enum type
		{
			HIGH	= 0,
			MEDIUM,
			LOW,
		};
	};



	//
	// Shader
	//

	class Shader
	{
		friend class ShaderProgram;

	// variables
	private:
		gles::GLuint	_id;
		gl_shader::type	_type;

	// methods
	private:
		void _Create ();
		void _Destroy ();

	public:
		Shader (gl_shader::type type);
		Shader (Shader &sh);
		~Shader ();

		bool SetSource (const char * source, usize length = 0);
		bool SetSource (uint count, const char * const * source, const usize * lengths = 0);
		bool Compile ();

		void GetLog (INOUT string &log) const;
		void ShowLog () const;

		gl_shader::type	ShaderType() const	{ return _type; }
	};



	//
	// Shader Program
	//

	class ShaderProgram
	{
		template <typename T>
		friend struct Uniform;

		template <typename T, usize SizeOf>
		friend struct UniformBuffer;

		friend class  Program;

	// variables
	private:
		gles::GLuint	_id;


	// methods
	private:
		void _Create ();
		void _Destroy ();

		bool _GetUniformInfo (gles::GLint location, OUT string &name, OUT gles::GLenum &type, OUT uint &size) const;

		static gles::GLuint _GetActiveProgram ();

	public:
		ShaderProgram ();
		~ShaderProgram ();

		bool Attach (const Shader &shader);
		bool Detach (const Shader &shader);
		
		bool BindAttrib (uni_c_string name, uint index);
		bool Link ();

		void GetLog (INOUT string &log) const;
		void ShowLog () const;

		gles::GLuint	Id () const	{ return _id; }
	};




//----------------------------- Shader ----------------------------//
	
	// constructor
	inline Shader::Shader (gl_shader::type type) :
		_id(0), _type(type)
	{}
	

	// constructor
	inline Shader::Shader (Shader &sh) :
		_id(0), _type(gl_shader::FRAGMENT)
	{
		_swap( this->_id,	sh._id );
		_swap( this->_type,	sh._type );
	}


	// destructor
	inline Shader::~Shader ()
	{
		_Destroy();
	}


	// _Create
	inline void Shader::_Create ()
	{
		using namespace gles;

		if ( _id == 0 )
			GL_CALL( _id = glCreateShader( _type ) );
	}


	// _Destroy
	inline void Shader::_Destroy ()
	{
		using namespace gles;

		if ( _id ) {
			GL_CALL( glDeleteShader( _id ) );
			_id = 0;
		}
		_type = gl_shader::_UNKNOWN;
	}


	// SetSource
	inline bool Shader::SetSource (const char *source, usize length)
	{
		using namespace gles;

		_Create();

		GL_CALL_R( glShaderSource( _id, 1, &source, (const GLint *)(length ? &length : 0) ), false );
		return true;
	}


	// SetSource
	inline bool Shader::SetSource (uint count, const char * const * source, const usize * lengths)
	{
		using namespace gles;

		_Create();

		GL_CALL_R( glShaderSource( _id, count, source, (const GLint *)(lengths ? &lengths : 0) ), false );
		return true;
	}


	// Compile
	inline bool Shader::Compile ()
	{
		using namespace gles;

		GL_CALL( glCompileShader( _id ) );
		
		GLint	i_res = 0;
		GL_CALL( glGetShaderiv( _id, GL_COMPILE_STATUS,  &i_res ) );
		
		return (i_res == GL_TRUE);
	}


	// GetLog
	inline void Shader::GetLog (INOUT string &log) const
	{
		using namespace gles;

		GLint	i_res = 0;
		GL_CALL( glGetShaderiv( _id, GL_COMPILE_STATUS,  &i_res ) );
		bool	compiled = (i_res == GL_TRUE);

		GLint	i_logsize = 0;
		GL_CALL( glGetShaderiv( _id, GL_INFO_LOG_LENGTH, &i_logsize ) );

		if ( i_logsize > 3 )
		{
			const char *	s_aPrefixes[] = { "Unknown shader type ",
											  "Vertex shader ",
											  "Fragment shader " };

			const char *	p_prefix = s_aPrefixes[0];

			switch ( _type )
			{
				case gl_shader::VERTEX	 :	p_prefix = s_aPrefixes[1];	break;
				case gl_shader::FRAGMENT :	p_prefix = s_aPrefixes[2];	break;
			};

			string	s_info;
			s_info.Reserve( i_logsize + 1 );

			GL_CALL( glGetShaderInfoLog( _id, i_logsize, null, s_info.ptr() ) );

			s_info.SetLength( i_logsize );

			log << p_prefix;

			if ( compiled )	log << "compilation message:\n";
			else			log << "compilation error:\n";
			
			log << s_info;
		}
	}


	// ShowLog
	inline void Shader::ShowLog () const
	{
		DEBUG_ONLY(
			string	log;
			GetLog( log );

			if ( not log.Empty() ) {
				LOG( log.cstr(), ELog::WARN );
			}
		)
	}


	
//---------------------------- ShaderProgram ----------------------------//

	// constructor
	inline ShaderProgram::ShaderProgram () : _id(0)
	{}


	// destructor
	inline ShaderProgram::~ShaderProgram ()
	{
		_Destroy();
	}


	// _Create
	inline void ShaderProgram::_Create ()
	{
		using namespace gles;

		if ( _id == 0 )
			GL_CALL( _id = glCreateProgram() );
	}


	// _Destroy
	inline void ShaderProgram::_Destroy ()
	{
		using namespace gles;

		if ( _id != 0 ) {
			GL_CALL( glDeleteProgram( _id ) );
			_id = 0;
		}
	}


	// Attach
	inline bool ShaderProgram::Attach (const Shader &shader)
	{
		using namespace gles;

		_Create();
		GL_CALL_R( glAttachShader( _id, shader._id ), false );
		return true;
	}
	

	// Detach
	inline bool ShaderProgram::Detach (const Shader &shader)
	{
		using namespace gles;

		GL_CALL_R( glDetachShader( _id, shader._id ), false );
		return true;
	}


	// BindAttrib
	inline bool ShaderProgram::BindAttrib (uni_c_string name, uint index)
	{
		using namespace gles;

		GL_CALL_R( glBindAttribLocation( _id, index, name.cstr() ), false );
		return true;
	}


	// Link
	inline bool ShaderProgram::Link ()
	{
		using namespace gles;

		GL_CALL( glLinkProgram( _id ) );
		
		GLint	i_res = 0;
		GL_CALL( glGetProgramiv( _id, GL_LINK_STATUS, &i_res ) );

		return (i_res == GL_TRUE);
	}


	// GetLog
	inline void ShaderProgram::GetLog (INOUT string &log) const
	{
		using namespace gles;

		GLint	i_res = 0;
		GL_CALL( glGetProgramiv( _id, GL_LINK_STATUS, &i_res ) );
		bool	linked = (i_res == GL_TRUE);

		GLint	i_logsize	= 0;
		GL_CALL( glGetProgramiv( _id, GL_INFO_LOG_LENGTH, &i_logsize ) );

		if ( i_logsize > 3 )
		{
			string	s_info;
			s_info.Reserve( i_logsize + 1 );

			GL_CALL( glGetProgramInfoLog( _id, i_logsize, nullptr, s_info.ptr() ) );

			s_info.SetLength( i_logsize );
			
			if ( linked )	log << "program linking message:\n";
			else			log << "program linking error:\n";

			log;
		}
	}


	// ShowLog
	inline void ShaderProgram::ShowLog () const
	{
		DEBUG_ONLY(
			string	log;
			GetLog( log );

			if ( not log.Empty() ) {
				LOG( log.cstr(), ELog::WARN );
			}
		)
	}
	

	// _GetActiveProgram
	inline gles::GLuint ShaderProgram::_GetActiveProgram ()
	{
		using namespace gles;

		GLint	prog_id = 0;

		glGetIntegerv( GL_CURRENT_PROGRAM, &prog_id );
		return prog_id;
	}
	

	// _GetUniformInfo
	inline bool ShaderProgram::_GetUniformInfo (gles::GLint location, OUT string &name, OUT gles::GLenum &type, OUT uint &count) const
	{
		using namespace gles;

		GLint	unif_count = 0,
				max_length = 0;

		GL_CALL_R( glGetProgramiv( _id, GL_ACTIVE_UNIFORMS,				&unif_count ), false );
		GL_CALL_R( glGetProgramiv( _id, GL_ACTIVE_UNIFORM_MAX_LENGTH,	&max_length ), false );

		string	buf;			buf.Reserve( max_length+2 );
		GLint	arr_size	= 0,
				name_length = 0;
		GLenum	unif_type	= 0;

		for (int i = 0; i < unif_count; ++i)
		{
			GL_CALL_R( glGetActiveUniform( _id, (GLuint)i, (GLsizei)buf.Capacity(), &name_length,
											&arr_size, &unif_type, buf.ptr() ), false );
			buf.SetLength( name_length );

			if ( buf.Back() == ']' )
			{
				FOR_rev( i, buf )
				{
					if ( buf[i] == '[' ) {
						buf.SetLength( i );
						break;
					}
				}
			}
			
			GLint	loc = -1;
			GL_CALL_R( loc = glGetUniformLocation( _id, buf.cstr() ), false );

			if ( loc == location )
			{
				count	= arr_size;
				type	= unif_type;
				name	= buf;
				return true;
			}
		}
		return false;
	}


}	// Graphics
}	// Engine